package src;

/**
 * Attractors by NiTh & M4rt
 */

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

import javax.media.opengl.GL;

import processing.core.PApplet;
import processing.opengl.PGraphicsOpenGL;

import com.sun.opengl.util.BufferUtil;

public class Main extends PApplet
{
	/**
	 * 
	 */
	private static final long	serialVersionUID	= 5420148812758840100L;

	/**
	 * 
	 * GL STUFF
	 * 
	 */

	public PGraphicsOpenGL		pgl					= null;
	public GL					gl					= null;

	public int[]				listVBO				= new int[2];

	private ByteBuffer			byteBuffer			= null;
	private FloatBuffer			floatBuffer			= null;

	/**
	 * ################### # Constants # ###################
	 */

	public final int			WIDTH				= 640;
	public final int			HEIGHT				= 480;

	public final float			DT					= 0.015f;
	public final int			NR_OF_TEST_PTS		= WIDTH * HEIGHT;
	public final int			SCALE				= 50;

	public final float			minX				= -4.0f;
	public final float			minY				= minX * HEIGHT / WIDTH;

	public final float			maxX				= 4.0f;
	public final float			maxY				= maxX * HEIGHT / WIDTH;

	/**
	 * ################### # Rest of vars # ###################
	 */

	public float				rotationX, rotationY;

	public float				angle				= 0.0f;
	public float				shift				= 0.0f;
	public boolean				currentShift		= true;

	public Vector4[]			pointList			= null;
	public Vector4[]			drawList			= null;

	public float				PRANDTL				= 10;
	public float				RAYLEIGH			= 28.0f;
	public float				BETA				= (8.0f / 3.0f);
	float						A, B, C, D, E;

	long						calcTime			= 0;
	long						drawTime			= 0;

	int							fps					= 0;
	int							frame				= 0;
	int							curCol				= 0;

	public void setup()
	{
		size(WIDTH, HEIGHT, OPENGL);

		pgl = (PGraphicsOpenGL) g;
		gl = pgl.gl;

		createLists();

		A = 1.7f;
		B = 1.7f;
		C = 0.06f;
		D = 1.2f;
		E = 1.0f;
	}

	public void draw()
	{
		stroke(255);
		text("Nr of pts: " + NR_OF_TEST_PTS, 20.0f, 20.0f);
		text(A + " " + B + " " + C + " " + D, 20.0f, 40.0f);
		// text(p + " " + r + " " + b, 20.0f, 40.0f);
		text("calcTime: " + calcTime + "ms", 20.0f, 60.0f);
		text("drawTime: " + drawTime + "ms", 20.0f, 80.0f);

		angle += 0.015;
		if (angle > TWO_PI) angle = 0.0f;
		//
		// if (currentShift)
		// shift += 0.1f;
		// else
		// shift -= 0.1f;
		//
		// if (shift > 20.0f) currentShift = false;
		// if (shift < 0.0f) currentShift = true;

		background(64, 64, 64);

		translate(width / 2, height / 2);

//		rotateX(angle);
//		rotateY((2 / 3) * angle);
//		rotateZ(angle * (1 / 3));

//		scale(20.0f);

		curCol = createHue((float) frame / NR_OF_TEST_PTS);

		// calcLorenz(p, r, b);
		calcPickover();
		// calcPickover2();
		drawPoints();

		PRANDTL += 0.01f;
		RAYLEIGH += 0.01f;
		BETA += 0.01f;

		A += 0.01f;
		B += 0.01f;
		C += 0.01f;
		D += 0.01f;

		frame++;
	}

	void drawPoints()
	{
		pgl.beginGL();

		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

		gl.glGenBuffers(2, listVBO, 0);

		gl.glBindBuffer(GL.GL_ARRAY_BUFFER, listVBO[0]);
		gl.glBufferData(GL.GL_ARRAY_BUFFER, NR_OF_TEST_PTS * 3
						* BufferUtil.SIZEOF_FLOAT, points2FloatBuffer(),
						GL.GL_DYNAMIC_DRAW);

		gl.glBindBuffer(GL.GL_ARRAY_BUFFER, listVBO[1]);
		gl.glBufferData(GL.GL_ARRAY_BUFFER, NR_OF_TEST_PTS * 3
						* BufferUtil.SIZEOF_BYTE, colors2ByteBuffer(),
						GL.GL_DYNAMIC_DRAW);

		gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL.GL_COLOR_ARRAY);

		gl.glBindBuffer(GL.GL_ARRAY_BUFFER, listVBO[0]);
		gl.glVertexPointer(3, GL.GL_FLOAT, 0, 0);

		gl.glBindBuffer(GL.GL_ARRAY_BUFFER, listVBO[1]);
		gl.glColorPointer(3, GL.GL_UNSIGNED_BYTE, 0, 0);

		gl.glDrawArrays(GL.GL_POINTS, 0, NR_OF_TEST_PTS);

		gl.glDisableClientState(GL.GL_COLOR_ARRAY);
		gl.glDisableClientState(GL.GL_VERTEX_ARRAY);

		pgl.endGL();
	}

	public void createLists()
	{
		pointList = new Vector4[NR_OF_TEST_PTS];
		drawList = new Vector4[NR_OF_TEST_PTS];
		
		for (int i = 0; i < pointList.length; i++)
		{
			pointList[i] = new Vector4();
			drawList[i] = new Vector4();
		}
	}

	public FloatBuffer points2FloatBuffer()
	{
		floatBuffer = BufferUtil.newFloatBuffer(NR_OF_TEST_PTS * 3);

		for (int i = 0; i < NR_OF_TEST_PTS; i++)
		{
			floatBuffer.put(drawList[i].x * SCALE);
			floatBuffer.put(drawList[i].y * SCALE);
			floatBuffer.put(drawList[i].z * SCALE);
		}

		floatBuffer.rewind();

		return floatBuffer;
	}

	public ByteBuffer colors2ByteBuffer()
	{
		byteBuffer = BufferUtil.newByteBuffer(NR_OF_TEST_PTS * 3);

		for (int i = 0; i < NR_OF_TEST_PTS; i++)
		{
			byteBuffer.put((byte) (red(drawList[i].col)));
			byteBuffer.put((byte) (green(drawList[i].col)));
			byteBuffer.put((byte) (blue(drawList[i].col)));
		}

		byteBuffer.rewind();

		return byteBuffer;
	}

	public void calcDrawList()
	{
		for (int i = 0; i < NR_OF_TEST_PTS; i++)
		{
			int xi = (int)((pointList[i].x - minX) * HEIGHT / (maxX - minX));
			int yi = (int)((pointList[i].y - minY) * WIDTH / (maxY - minY));
			
			if (xi >= 0 && xi < WIDTH && yi >= 0 && yi < HEIGHT)
				drawList[xi + yi * WIDTH] = pointList[i];
		}
	}
	
	public void calcPickover()
	{
		pointList[0].x = 10.0f;
		pointList[0].y = 0.0f;
		pointList[0].z = 0.0f;
		pointList[0].col = 0;

		for (int i = 1; i < NR_OF_TEST_PTS; i++)
		{
			/**
			 * ORIGINAL PICKOVER (DE JONG IN 2D)
			 */
			// pointList[i].x = (sin(A * pointList[i - 1].y) - pointList[i -
			// 1].z * cos(B * pointList[i - 1].x));
			// pointList[i].y = (pointList[i - 1].z * sin(C * pointList[i -
			// 1].x) + cos(D * pointList[i - 1].y);
			// pointList[i].z = (sin(pointList[i - 1].x));

			/**
			 * MY MOD OF PICKOVER - PICKOVER ZIĘTKA
			 */
//			pointList[i].x = (sin(A * pointList[i - 1].y) - (float) Math.pow(
//							pointList[i - 1].z, 2)
//							* cos(B * pointList[i - 1].x));
//			pointList[i].y = (float) Math.pow(pointList[i - 1].z, 2)
//							* sin(C * pointList[i - 1].x)
//							+ cos(D * pointList[i - 1].y);
			
			pointList[i].x = sin(A * pointList[i - 1].x) + cos(B * pointList[i - 1].y);
			pointList[i].y = sin(C * pointList[i - 1].y) + cos(D * pointList[i - 1].x);			
		}
		
		calcDrawList();
		
		for (int i = 0; i < NR_OF_TEST_PTS; i++)
		{
//			xi = (int)((drawList[i].x - minX) * HEIGHT / (maxX - minX));
//			yi = (int)((drawList[i].y - minY) * WIDTH / (maxY - minY));
//			
//			if (xi >= 0 && xi < WIDTH && yi >= 0 && yi < HEIGHT)
//				drawList[xi + yi * WIDTH].col += addColor(drawList[xi + yi * WIDTH].col, curCol);
			
			drawList[i].col = addColor(drawList[i].col, curCol);
		}
	}

	public void calcPickover2()
	{
		pointList[0].x = 10.0f;
		pointList[0].y = 0.0f;
		pointList[0].z = 0.0f;

		for (int i = 1; i < NR_OF_TEST_PTS; i++)
		{
			pointList[i].x = (sin(A * pointList[i - 1].x) - pointList[i - 1].z
							* cos(B * pointList[i - 1].y));
			pointList[i].y = (pointList[i - 1].z * sin(C * pointList[i - 1].x) - cos(D
							* pointList[i - 1].y));
			pointList[i].z = (E / sin(pointList[i - 1].x));
		}
	}

	public void calcLorenz()
	{
		pointList[0].x = 20.0f;
		pointList[0].y = -30.0f;
		pointList[0].z = 0.0f;
		pointList[0].col = 0;

		for (int i = 1; i < NR_OF_TEST_PTS; i++)
		{
			int curCol = createHue(i / NR_OF_TEST_PTS);

			pointList[i].x = pointList[i - 1].x
							+ (PRANDTL * (pointList[i - 1].y - pointList[i - 1].x))
							* DT;
			pointList[i].y = pointList[i - 1].y
							+ ((-pointList[i - 1].x * pointList[i - 1].z)
											+ (RAYLEIGH * pointList[i - 1].x) - pointList[i - 1].y)
							* DT;
			pointList[i].z = pointList[i - 1].z
							+ ((pointList[i - 1].x * pointList[i - 1].y) - (BETA * pointList[i - 1].z))
							* DT;
			pointList[i].col = addColor(pointList[i].col, curCol);
		}
	}

	public int createHue(float h)
	{
		h *= 6.0f;
        int hi = (int)(h * 255);
        float hf = h - (float)hi;
        
        switch(hi % 6)
        {
                case 0:
                {
                        return color(255, (byte)(hf * 255), 0);
                }
                
                case 1:
                {
                        return color(255 - (byte)(hf * 255), 255, 0);
                }
                
                case 2:
                {
                        return color(0 , 255, (byte)(hf * 255));
                }
                
                case 3:
                {
                        return color(0, 255 - (byte)(hf * 255), 255);
                }
                
                case 4:
                {
                        return color((byte)(hf * 255), 0, 255);
                }
                
                case 5:
                {
                        return color(255, 0, 255 - (byte)(hf * 255));
                }
        }
        
        return color(0);
	}

	public int addColor(int color, int col)
	{
		return color((byte) (red(color) + red(col)),
						(byte) (green(color) + green(col)),
						(byte) (blue(color) + blue(col)));
	}
}
